package com.guojing.jl.nio.selector;

import com.guojing.jl.nio.bytebuff.Test8ByteBufferExam;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 *
 * 消息边界问题
 *
 * @author: guojing
 * @create: 2023/12/7 12:46
 */
@Slf4j
public class SelectorServerMessageBound {


    /**
     * selector管理多个channel
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        //1.创建selector，一个selector管理多个channel
        Selector selector = Selector.open();

        //2.启动serverchannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置非阻塞模式
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        //3.建立selector和channel的关系
        SelectionKey serverChannelSelKey = serverSocketChannel.register(selector, 0, null);
        log.info("serverChannelSelKey={}",serverChannelSelKey);
        //只关注accept事件
        serverChannelSelKey.interestOps(SelectionKey.OP_ACCEPT);

        while (true){
            //4.select 方法。没有时间发生时阻塞，不用空执行
            selector.select();
            //5.处理事件: 所有发生的事件,事件要么处理要么取消，不能什么也不做
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();

                //接下来要处理，必须删除key，否则事件还会继续被处理，可能导致空指针
                iterator.remove();

                log.info("key={},serverChannelSelKey={}",key,serverChannelSelKey);

                if(key.isAcceptable()){
                    //channel等于 serverSocketChannel
                    ServerSocketChannel currServerSocketChannel = (ServerSocketChannel) key.channel();
                    //accept表示处理事件
                    SocketChannel socketChannel = currServerSocketChannel.accept();
                    //非阻塞模式
                    socketChannel.configureBlocking(false);

                    //每一个socketchannel注册自己的附件：bytebuffer
                    ByteBuffer byteBuffer = ByteBuffer.allocate(10);
                    SelectionKey socketChannelSelectionKey = socketChannel.register(selector, 0, byteBuffer);
                    socketChannelSelectionKey.interestOps(SelectionKey.OP_READ);
                    log.info("socketChannel={},socketChannelSelectionKey={}", socketChannel, socketChannelSelectionKey);


                }else if(key.isReadable()){

                    try{
                        SocketChannel currSocketChannel = (SocketChannel) key.channel();
                        //获取注册的附件
                        ByteBuffer byteBuffer = (ByteBuffer)key.attachment();
                        //可能出现消息边界问题
                        int read = currSocketChannel.read(byteBuffer);
                        if(read == -1){
                            //如果是客户端正常断开，也会产生一个读事件，并且返回-1
                            key.cancel();
                        }else{
                            //边界处理
                            Test8ByteBufferExam.split(byteBuffer);
                            if(byteBuffer.position() == byteBuffer.limit()){
                                //边界处理之后，消息没有任务读取，也就是没都读取到\n
                                ByteBuffer newByteBuffer = ByteBuffer.allocate(byteBuffer.capacity() * 2);
                                //newByteBuffer的时候需要从byteBUffer读取
                                byteBuffer.flip();
                                newByteBuffer.put(byteBuffer);
                                key.attach(newByteBuffer);
                            }

                            log.info("currSocketChannel={},key={}",currSocketChannel,key);
                        }

                    }catch (Exception e){
                        //客户端强制断开之后会出现异常： 客户单关闭之后出产生一个read事件，所以需要处理这个read事件,此处取消事件
                        key.cancel();
                    }


                }
            }

        }

    }

}
